<?php
/**
 * Controller Base class.
 * Prepares and renders content for given models action.
 * @package PersonalHomePageFramework
 * @subpackage Controller
 * @category Core
 * @author Rolands Atvars <rolands.atvars@gmail.com>
 * @copyright Copyright (c) 2009, Rolands Atvars
 * @license http://opensource.org/licenses/gpl-license.php GNU General Public License version 3 (GPLv3)
 */
abstract class ControllerBase {
    /**
     * @var string default layout to wrap the rendered action contents. Defaults to "default".
     * @access public
     */
    public $default_layout = "default";
    /**
     * @var bool can given Controller be accessed from URL.
     * @access public
     */
    public $visible_from_url = true;
    /**
     * @var string contains currently active action.
     * @access protected
     */
    protected $current_action;
    /**
     * @var bool does Controller have to render default actions template (template name is the same as action). Defaults to true.
     * @access protected
     */
    protected $render_default_template = true;
    /**
     * @var array contains content for different place content. Used in render_contents()
     * @access protected
     */
    protected $content_for = array();
    /**
     * @var array all the filters that has to be run before current action renders. Structure array("filter_name" => "list, of, actions, devided, by, commas")
     * @access protected
     */
    protected $before_filters = array();
    
    /**
     * Initializes given Controller and renders given action.
     * @param string $action name of the action to render
     * @global array $output contains all the contents to be rendered in layout
     * @throws Undefined action exception only if site is not in Production mode (in this case renders 404 error). Exception is triggered if given action does not exist.
     */
    public function __construct($action) {
        global $output;
        
        $this->current_action = $action;
        
        if(method_exists(get_class($this), "_" . $action)) {
            ob_start();
            
            $can_render_action = $this->run_before_filters();
            if($can_render_action === true) {
              $content = $this->{"_" . $action}();
              if($this->render_default_template) {
                $dir = underscorify(get_class($this));
                $dir = substr($dir, 0, -11);
                $this->render_template($dir, $this->current_action);
              }
              $content = empty($content) ? "html" : $content;
            }
            else {
              $content = "html";
            }
            $output[$content] .= ob_get_contents();
            
            ob_end_clean();
            
            if(!empty($this->content_for)) {
              foreach($this->content_for as $key => $value) {
                $output[$key] .= $value;
              }
            }
        }
        else {
          if(IS_PRODUCTION) {
            render_404_error();
          }
          else {
            throw new Exception("Undefined action: <b>$action</b> for class: " . get_class($this));
          }
        }
    }
    
    /**
    * Sets public attribute if it does not exist for current class.
    * Cannot be called directly.  Fires every time you set non-existing attribute.
    * @access public
    * @param string $name new attribute name
    * @param mixed $value new attributes value
      */
    public function __set($name, $value) {
      $this->{$name} = $value;
    }
    
    /**
     * Runs all the before filters (defined in $before_filters attribute) before executing given action.
     * @access protected
     * @return bool returns false if some before_filter returns false
     */
    protected function run_before_filters() {
      foreach($this->before_filters as $key => $value) {
        $actions = explode(",", $value);
        foreach($actions as $action) {
          if($this->current_action == trim($action)) {
            $result = $this->{$key}();
            if($result == false) {
              return false;
            }
          }
        }
      }
      
      return true;
    }
    
    /**
     * Tells controller that there is no need to render current actions default template
     * @access protected
     */
    protected function render_nothing() {
      $this->render_default_template = false;
    }
    
    /**
     * Includes template file for the current action
     * @access protected
     * @param string $class_name which models template to render
     * @param string $action which actions template to render
     */
    protected function render_template($class_name, $action) {
        $this->render_default_template = false;
        include(VIEWS_PATH . $class_name ."/" . $action . ".php");
    }
    
    /**
     * Includes template file for given partial and sets class attributes if any given
     * @access protected
     * @param string $model in which models directory the partial exists
     * @param string $partial name of the partial file (you must not provide the underscore in beginning)
     * @param array $locals variables to be set as class attributes. Must be associative array witch strucutre "variable" => "value"
     */
    protected function render_partial($model, $partial, $locals = array()) {
        foreach($locals as $key => $value) {
            $this->set($key, $value);
        }
        include(VIEWS_PATH . $model ."/_" . $partial . ".php");
    }
    
    /**
     * Runs given Controllers action
     * @access protected
     * @param string $class_name Model name whose Controller to run
     * @param string $action which action to run
     */
    protected function render_action($class_name, $action) {
        $class = ucfirst($class_name) . "Controller";
        $class = new $class($action);
    }
    
    /**
     * Sets content for different part in layout (not standart 'html' part) so that you could use render_contents('some_name')
     * @access public
     * @param string $place place where to show given content (will be rendered like this render_contents($place))
     * @param string $content content to render in given place
     */
    public function render_for($place, $content) {
      $this->content_for[$place] = $content;
    }
    
    /**
     * Sets class attribute with given value (if any)
     * @access protected
     * @param string $name new attributes name
     * @param mixed $value optional value of new attribute. Defaults to null.
     */
    protected function set($name, $value = null) {
        $this->{$name} = $value;
    }
}
?>